/*
 * Galaxium Messenger
 * Copyright (C) 2005-2007 Philippe Durand <draekz@gmail.com>
 * Copyright (C) 2005-2007 Ben Motmans <ben.motmans@gmail.com>
 * 
 * License: GNU General Public License (GPL)
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

using System;
using System.Collections.Generic;
using System.IO;
using System.Web.Services.Protocols;

using Anculus.Core;

using Galaxium.Core;
using Galaxium.Protocol.Msn.Soap;
using Galaxium.Protocol.Msn.Soap.BodyParts;

namespace Galaxium.Protocol.Msn
{
	public class MsnContact : AbstractContact, IMsnEntity, IComparable<MsnContact>
	{
		public event EventHandler DirectBridgeEstablished;
		
		MsnListType _listType;
		string _guid;
		MsnCurrentMedia _currentMedia;
		
		string _homePhoneNumber;
		string _workPhoneNumber;
		string _mobilePhoneNumber;

		bool _isMobileContact;
		long _membershipId;
		
		MsnClientIdentifier _clientIdentifier;
		List<MsnEmoticon> _sentEmoticons;
		Network _network;
		
		IMsnP2PBridge _directBridge = null;
		
		public IMsnP2PBridge DirectBridge
		{
			get { return _directBridge; }
		}
		
		public override string DisplayName
		{
			get { return base.DisplayName; }
			set
			{
				base.DisplayName = value;
				Save ();
			}
		}

		public override string DisplayMessage
		{
			get { return base.DisplayMessage; }
			set
			{
				base.DisplayMessage = value;
				Save ();
			}
		}
		
		public override IDisplayImage DisplayImage
		{
			get
			{
				if (base.DisplayImage != null)
					return base.DisplayImage;
				
				return MsnObject.Load (Session as MsnSession, _config.GetString ("LastDisplayImage")) as MsnDisplayImage;
			}
			set
			{
				base.DisplayImage = value;
				
				if ((value is MsnDisplayImage) && ((value as MsnDisplayImage).Creator != this))
					Log.Warn ("Contact {0} has display image with creator {1}", UniqueIdentifier, (value as MsnDisplayImage).Creator.UniqueIdentifier);
				
				if (value is MsnDisplayImage)
					_config.SetString ("LastDisplayImage", ((MsnDisplayImage)value).Context);
				else
					_config.SetString ("LastDisplayImage", "0");
			}
		}
		
		public long MembershipId
		{
			get { return _membershipId; }
			set { _membershipId = value; }
		}
		
		public new MsnSession Session
		{
			get { return base.Session as MsnSession; }
		}
		
		public MsnContact (MsnSession session, string passport, Network network)
			: base (session, passport, passport, MsnPresence.Offline)
		{
			_network = network;
			_currentMedia = new MsnCurrentMedia ();
			_homePhoneNumber = String.Empty;
			_workPhoneNumber = String.Empty;
			_mobilePhoneNumber = String.Empty;
			
			_sentEmoticons = new List<MsnEmoticon>();
		}

		public MsnContact (MsnSession session, string passport)
			: this (session, passport, Network.WindowsLive)
		{
				
		}
		
		protected override void OnDisplayImageChange (EntityChangeEventArgs<IDisplayImage> args)
		{
			if (args.Old is MsnObject)
				(args.Old as MsnObject).DataChanged -= DisplayImageDataChanged;
			
			if (args.New is MsnObject)
			{
				(args.New as MsnObject).DataChanged += DisplayImageDataChanged;
				(args.New as MsnObject).Request ();
				
				if ((args.New as MsnDisplayImage).Dynamic != null)
				{
					(args.New as MsnDisplayImage).Dynamic.DataChanged += DisplayImageDataChanged;
					(args.New as MsnDisplayImage).Dynamic.Request ();
				}
			}
			
			base.OnDisplayImageChange (args);
		}
		
		protected override void OnPresenceChange (EntityChangeEventArgs<IPresence> args)
		{
			base.OnPresenceChange (args);
			
			if (args.New != MsnPresence.Offline)
			{
				// If a contact comes online and we don't have their display pic
				// then we can request it
				
				if (DisplayImage is MsnObject)
				{
					((MsnObject)DisplayImage).Request ();
					
					if ((DisplayImage as MsnDisplayImage).Dynamic != null)
					{
						(DisplayImage as MsnDisplayImage).Dynamic.DataChanged += DisplayImageDataChanged;
						(DisplayImage as MsnDisplayImage).Dynamic.Request ();
					}
				}
			}
		}
		
		void DisplayImageDataChanged (object sender, EventArgs args)
		{
			OnDisplayImageChange (new EntityChangeEventArgs<IDisplayImage> (this, DisplayImage, DisplayImage));
		}
		
		public void SetDisplayName (string displayName, bool save)
		{
			base.SetDisplayName (displayName);
			
			if (save)
				Save ();
		}
		
		internal void SetUserDisplay (MsnDisplayImage userDisplay)
		{
			DisplayImage = userDisplay;
		}
		
		public string Guid
		{
			get { return this._guid; }
			internal set { this._guid = value; }
		}

		public bool IsGuidSet
		{
			get { return _guid != null; }
		}
		
		public MsnListType ListType
		{
			get { return _listType; }
			set { _listType = value; }
		}

		public bool IsInList (MsnListType list)
		{
			return (_listType & list) == list;
		}

		public int CompareTo (MsnContact other)
		{
			return UniqueIdentifier.CompareTo (other.UniqueIdentifier) +
				   Network.CompareTo (other.Network);
		}
		
		public Network Network
		{
			get { return _network; }
		}

		public bool IsBlocked
		{
			get { return IsInList (MsnListType.Blocked); }
		}

		public MsnCurrentMedia CurrentMedia
		{
			get { return _currentMedia; }
			set { _currentMedia = value; }
		}

		public string HomePhoneNumber
		{
			get { return this._homePhoneNumber; }
			set { this._homePhoneNumber = value; }
		}

		public string WorkPhoneNumber
		{
			get { return this._workPhoneNumber; }
			set { this._workPhoneNumber = value; }
		}

		public string MobilePhoneNumber
		{
			get { return this._mobilePhoneNumber; }
			set { this._mobilePhoneNumber = value; }
		}

		public bool IsMobileContact
		{
			get { return this._isMobileContact; }
			set { this._isMobileContact = value; }
		}

		public MsnClientIdentifier ClientIdentifier
		{
			get { return _clientIdentifier; }
			set { _clientIdentifier = value; }
		}
		
		public List<MsnEmoticon> SentEmoticons
		{
			get { return _sentEmoticons; }
		}
		
		protected void OnDirectBridgeEstablished ()
		{
			if (DirectBridgeEstablished != null)
				DirectBridgeEstablished (this, EventArgs.Empty);
		}
		
#region Management Operations
		internal void RemoveFromLists (MsnListType lists)
		{
			RMLCommand rml = new RMLCommand (Session);
			
			ListCommand.ListItem item = new ListCommand.ListItem ();
			item.Contact = this;
			item.ListType = lists;

			rml.Add (item);
			
			this.ListType &= ~lists;
			
			Session.Connection.Send (rml);
		}
		
		internal void AddToLists (MsnListType lists)
		{
			ADLCommand adl = new ADLCommand (Session, false);

			ListCommand.ListItem item = new ListCommand.ListItem ();
			item.Contact = this;
			item.ListType = lists;
			
			adl.Add (item);
			
			this.ListType |= lists;
			
			Session.Connection.Send (adl);
		}
		
		internal void AddToMemberRole (MemberRole role, params ExceptionDelegate[] done)
		{
			Log.Debug ("Add {0} to role '{1}'", UniqueIdentifier, role);
			
			ServiceHandle serviceHandle = new ServiceHandle ();
			serviceHandle.Id = 0;
			serviceHandle.Type.Name = "Messenger";
			
			Membership membership = new Membership ();
			membership.MemberRole = role;
			
			PassportMember member = new PassportMember ();
			member.Type = MemberType.Passport;
			member.State = MemberState.Accepted;
			member.PassportName = UniqueIdentifier;
			
			membership.Members.Add (member);
			
			MembershipCollection memberships = new MembershipCollection ();
			memberships.Add (membership);
			
			Session.SharingService.BeginAddMember (serviceHandle, memberships, delegate (IAsyncResult deleteResult)
			{
				try
				{
					Session.SharingService.EndAddMember (deleteResult);
					
					foreach (ExceptionDelegate del in done)
						del (null);
				}
				catch (SoapException ex)
				{
					//Log.Error ("Error adding member '{0}' to role '{1}': {2} ({3})", UniqueIdentifier, role, ex.Code.Name, ex.Message);
					
					foreach (ExceptionDelegate del in done)
						del (ex);
					
					return;
				}
				catch (Exception ex)
				{
					//Log.Error (ex, "Error adding member '{0}' to role '{1}'", UniqueIdentifier, role);
					
					foreach (ExceptionDelegate del in done)
						del (ex);
					
					return;
				}
			}, null);
		}
		
		internal void RemoveFromMemberRole (MemberRole role, params ExceptionDelegate[] done)
		{
			Log.Debug ("Remove {0} from role '{1}'", UniqueIdentifier, role);
			
			ServiceHandle serviceHandle = new ServiceHandle ();
			serviceHandle.Id = 0;
			serviceHandle.Type.Name = "Messenger";
			
			Membership membership = new Membership ();
			membership.MemberRole = role;
			
			PassportMember member = new PassportMember ();
			member.Type = MemberType.Passport;
			member.State = MemberState.Accepted;
			member.PassportName = UniqueIdentifier;
			
			membership.Members.Add (member);
			
			MembershipCollection memberships = new MembershipCollection ();
			memberships.Add (membership);
			
			Session.SharingService.BeginDeleteMember (serviceHandle, memberships, delegate (IAsyncResult deleteResult)
			{
				try
				{
					Session.SharingService.EndDeleteMember (deleteResult);
					
					foreach (ExceptionDelegate del in done)
						del (null);
				}
				catch (SoapException ex)
				{
					//Log.Error ("Error deleting member '{0}' from role '{1}': {2} ({3})", UniqueIdentifier, role, ex.Code.Name, ex.Message);
					
					foreach (ExceptionDelegate del in done)
						del (ex);
					
					return;
				}
				catch (Exception ex)
				{
					//Log.Error (ex, "Error deleting member '{0}' from role '{1}'", UniqueIdentifier, role);
					
					foreach (ExceptionDelegate del in done)
						del (ex);
					
					return;
				}
			}, null);
		}
		
		public void Block (params ExceptionDelegate[] done)
		{
			RemoveFromLists (MsnListType.Allowed);
			AddToLists (MsnListType.Blocked);
			
			try
			{
				Session.EmitContactChanged (new ContactEventArgs (this));
			}
			catch (Exception ex)
			{
				Log.Error (ex, "Error in sessions ContactChanged event");
			}
			
			RemoveFromMemberRole (MemberRole.Allow, new ExceptionDelegate (delegate (Exception remEx)
			{
				if (remEx != null)
				{
					Log.Error (remEx, "Error blocking contact (whilst removing from Allow)");
					
					foreach (ExceptionDelegate del in done)
						del (remEx);
					
					return;
				}
				
				AddToMemberRole (MemberRole.Block, new ExceptionDelegate (delegate (Exception addEx)
				{
					if (addEx != null)
					{
						Log.Error (addEx, "Error blocking contact (whilst adding to Block)");
						
						foreach (ExceptionDelegate del in done)
							del (addEx);
						
						return;
					}
					
					Log.Debug ("Contact {0} blocked successfully", UniqueIdentifier);
					
					foreach (ExceptionDelegate del in done)
						del (null);
				}));
			}));
		}
		
		public void Unblock (params ExceptionDelegate[] done)
		{
			RemoveFromLists (MsnListType.Blocked);
			AddToLists (MsnListType.Allowed);
			
			try
			{
				Session.EmitContactChanged (new ContactEventArgs (this));
			}
			catch (Exception ex)
			{
				Log.Error (ex, "Error in sessions ContactChanged event");
			}
			
			RemoveFromMemberRole (MemberRole.Block, new ExceptionDelegate (delegate (Exception remEx)
			{
				if (remEx != null)
				{
					Log.Error (remEx, "Error un-blocking contact (whilst removing from Block)");
					
					foreach (ExceptionDelegate del in done)
						del (remEx);
					
					return;
				}
				
				AddToMemberRole (MemberRole.Allow, new ExceptionDelegate (delegate (Exception addEx)
				{
					if (addEx != null)
					{
						Log.Error (addEx, "Error un-blocking contact (whilst adding to Allow)");
						
						foreach (ExceptionDelegate del in done)
							del (addEx);
						
						return;
					}
					
					Log.Debug ("Contact {0} un-blocked successfully", UniqueIdentifier);
					
					foreach (ExceptionDelegate del in done)
						del (null);
				}));
			}));
		}
#endregion
	}
}